package security;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;


public class AES256 {

	//source : http://stackoverflow.com/questions/15554296/simple-java-aes-encrypt-decrypt-example
	//debug post 1
	//passage a 256 au lieu de 128 : http://stackoverflow.com/questions/6561771/java-aes-256-secure-key-generator-illegal-key-size

	//à voir = utilisation clé perso : http://stackoverflow.com/questions/992019/java-256-bit-aes-password-based-encryption

	public static void testEncrypt() {
		try {
			String s = "Hello there. How are you? Have a nice day.";

			// Generate key
			KeyGenerator kgen = KeyGenerator.getInstance("AES");
			kgen.init(256);
			SecretKey aesKey = kgen.generateKey();

			// Encrypt cipher
			Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

			// Encrypt
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
			cipherOutputStream.write(s.getBytes());
			cipherOutputStream.flush();
			cipherOutputStream.close();
			byte[] encryptedBytes = outputStream.toByteArray();
			outputStream.close(); //rajouté, debug

			// Decrypt cipher
			Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			//IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
			byte[] iv = encryptCipher.getIV(); //rajouté, debug
			IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); //rajouté, debug
			decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

			// Decrypt
			outputStream = new ByteArrayOutputStream();
			ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
			CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
			byte[] buf = new byte[1024];
			int bytesRead;
			while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
				outputStream.write(buf, 0, bytesRead);
			}

			System.out.println("Result: " + new String(outputStream.toByteArray()));
			cipherInputStream.close(); //rajouté, debug
			outputStream.close(); //rajouté, debug

		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public static void createAESKeyStore(String path, char[] storepass, char[] keypass, String AESAliasName){
		String ksName = path;
		String aliasKey = AESAliasName;
		final char[] storePass = storepass;
		final char[] keyPass = keypass;
		
		KeyGenerator kgen;
		try {
			kgen = KeyGenerator.getInstance("AES");
			kgen.init(256);
			SecretKey skey = kgen.generateKey();
			KeyStore ks = KeyStore.getInstance("JCEKS", "SunJCE");
			ks.load(null, storePass);
			ks.setKeyEntry(aliasKey, skey, keyPass, null);
			FileOutputStream fos = new FileOutputStream(ksName);
			ks.store(fos, storePass);
			fos.close();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	public static SecretKey loadKeyFromKeyStore(String path, char[] storepass, char[] keypass, String AESAliasName){
		final char[] storePass = storepass;
		final char[] keyPass = keypass;
		KeyStore ks;
		SecretKey skey = null;
		
		try {
			ks = KeyStore.getInstance("JCEKS", "SunJCE");
			FileInputStream kis = new FileInputStream(path);
			ks.load(kis, storePass);
			skey = (SecretKey) ks.getKey("aesKey", keyPass);
			kis.close();
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchProviderException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return skey;
	}
	
	public static SecretKey generateRandomKey(){
		KeyGenerator kgen;
		SecretKey aesKey = null;
		try {
			kgen = KeyGenerator.getInstance("AES");
			kgen.init(256);
			aesKey = kgen.generateKey();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return aesKey;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		//testEncrypt();

		System.out.println("Test propre:");
		String data = "Plip Plip Tagada Tsoin Tsoin On Teste =D";
		System.out.println("Texte initial:\t"+data);
		

		final String ksPath = "C:\\Users\\B182JS\\workspace\\projet_SmartCard\\bin\\aesKeyStore.keystore";
		final char[] ksPassword = {'t','o','t','o'};
		final char[] keyPassword = {'t','o','t','o'};
		String alias = "aesKey";
		
		createAESKeyStore(ksPath, ksPassword, keyPassword, alias);
		
		SecretKey aesKey = null;
		
		try {
			// Load Key From Keystore
			//aesKey = AES256.loadKeyFromKeyStore(ksPath, ksPassword, keyPassword, alias);
			
			// Generate key
			aesKey = AES256.generateRandomKey();

			// Encrypt cipher
			Cipher encryptCipher;
			encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);
			
			// Crypt and Decrypt
			byte[] encryptedData = encrypt(data.getBytes(),aesKey,encryptCipher);
			byte[] decryptedData = decrypt(encryptedData,aesKey,encryptCipher);
			
			// Show Result
			System.out.println("Résultat:\t"+new String(decryptedData));

		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}

		/*System.out.println("\nTest image:");

		String imgPath = "C:\\Documents and Settings\\Guillaume\\workspace\\tests\\lena.bmp";
		String imgPathCrypt = "C:\\Documents and Settings\\Guillaume\\workspace\\tests\\lena_crypt.bmp";
		String imgPath2 = "C:\\Documents and Settings\\Guillaume\\workspace\\tests\\lena_.bmp";

		FileInputStream r1, r2 = null;
		FileOutputStream w1, w2 = null;

		int offset_to_data = -14;
		int tmp = 0, b = 0;
		String buffer = "";
		byte[] bmp, buff2;
		int BUF_LEN = 1024;
		long timestamp = System.currentTimeMillis();

		try {
			KeyGenerator kgen2 = KeyGenerator.getInstance("AES");
			kgen2.init(256);
			SecretKey aesKey2 = kgen2.generateKey();

			// Encrypt cipher
			Cipher encryptCipher2;
			encryptCipher2 = Cipher.getInstance("AES/CBC/PKCS5Padding");
			encryptCipher2.init(Cipher.ENCRYPT_MODE, aesKey2);
			
			//chiffrement image
			r1 = new FileInputStream(imgPath);			
			w1 = new FileOutputStream(imgPathCrypt);

			// skip header (10 octets)
			for(int i=10;i>0;i--)
				w1.write(r1.read());

			// read offset (be) to bmp data, converted to le
			for(int i=0;i<4;i++) {
				tmp = r1.read();
				w1.write(tmp);
				offset_to_data += tmp*Math.pow(256, i);
			}

			// skip to data
			for(int i=offset_to_data;i>0;i--)
				w1.write(r1.read());
			
			DataInputStream dis = new DataInputStream(r1);
			int tailleImage = dis.available();
			System.out.println("Taille image : "+tailleImage+" et offset_to_data "+offset_to_data);
			
			bmp = new byte[tailleImage];
			buff2 = new byte[tailleImage];

			int z = 0;
			while(r1.read(bmp) != -1){
				//System.out.println("iteration "+z);
				z++;
				buff2 = encrypt(bmp,aesKey2,encryptCipher2);
				w1.write(buff2);
			}
			
			r1.close();
			w1.close();
			System.out.println("Temps chiffrement: "+(System.currentTimeMillis()-timestamp)+"ms");
			
			//dechiffrement image
			timestamp = System.currentTimeMillis();
			r2 = new FileInputStream(imgPathCrypt);
			w2 = new FileOutputStream(imgPath2);
			
			//bmp = new byte[BUF_LEN];
			//buff2 = new byte[BUF_LEN];
			//System.out.println(r.available());

			// skip header (10 octets)
			for(int i=10;i>0;i--)
				w2.write(r2.read());

			// read offset (be) to bmp data, converted to le
			offset_to_data = -14;
			for(int i=0;i<4;i++) {
				tmp = r2.read();
				w2.write(tmp);
				offset_to_data += tmp*Math.pow(256, i);
			}

			// skip to data
			for(int i=offset_to_data;i>0;i--)
				w2.write(r2.read());
			
			DataInputStream dis2 = new DataInputStream(r2);
			int tailleImage2 = dis2.available();
			System.out.println("Taille image : "+tailleImage+" et offset_to_data "+offset_to_data);
			
			bmp = new byte[tailleImage2];
			buff2 = new byte[tailleImage2];

			z = 0;
			while(r2.read(bmp) != -1){
				//System.out.println("iteration2 "+z);
				z++;
				buff2 = decrypt(bmp,aesKey2,encryptCipher2);
				w2.write(buff2);
			}

			r2.close();
			w2.close();
			
			System.out.println("Temps dechiffrement: "+(System.currentTimeMillis()-timestamp)+"ms");
			
		} catch (IOException e1) {
			e1.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		}*/
		
	}

	//version propre :

	public static byte[] encrypt(byte[] data, SecretKey aesKey, Cipher encryptCipher) {
		try {
			//String s = data;

			// Encrypt cipher
			//Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			//encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

			// Encrypt
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
			cipherOutputStream.write(data);
			cipherOutputStream.flush();
			cipherOutputStream.close();
			byte[] encryptedBytes = outputStream.toByteArray();
			outputStream.close(); //rajouté, debug

			return encryptedBytes;

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	public static byte[] decrypt(byte[] data, SecretKey aesKey, Cipher encryptCipher) {
		try {
			//String s = data;

			// Encrypt cipher
			//Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			//encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

			// Decrypt cipher
			Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			byte[] iv = encryptCipher.getIV(); //rajouté, debug
			IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); //rajouté, debug
			decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

			// Decrypt
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			ByteArrayInputStream inStream = new ByteArrayInputStream(data);
			CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
			byte[] buf = new byte[1024];
			int bytesRead;
			while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
				outputStream.write(buf, 0, bytesRead);
			}

			//System.out.println("Result: " + new String(outputStream.toByteArray()));
			cipherInputStream.close(); //rajouté, debug
			outputStream.close(); //rajouté, debug

			return outputStream.toByteArray();

		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

}
